import { baseURL } from '@/config/index'
import { IsArray, ObjectKeys } from '@/util'
import { clearLoginInfo } from '@/util/login'
import { toRoute } from '@/util/router'
import { clearToken, getToken, tplLang } from '@/util/store'
import { Routes } from '@vue/router/routes'
import Axios from 'axios'
import { Message } from 'element-ui'
import { downfile, fileurl } from '../api/v1/file'

const http = Axios.create({
  baseURL,
  timeout: 30 * 1000,
  withCredentials: true
})
const cancelToken = Axios.CancelToken
const cancelTokenMap = new Map()
//不需要并发控制的接口
const ccc = [
  // 数据字典接口
  '/getOptionsByKey'
]
const cancelMsg = key => `cancel ${key}`
// 退出并发控制列队
const deleteCancelTokenMap = config => {
  const { method, url } = config || {}
  config &&
    (method && url
      ? cancelTokenMap.delete(method + url)
      : console.log(
          `取消请求失败 method: ${method}, url: ${url}, config: ${config}`
        ))
}
// 清空并发控制列队
const clearCancelTokenMap = () =>
  cancelTokenMap.forEach((cancel, key) => {
    cancelTokenMap.delete(key)
    cancel(cancelMsg(key))
  })

// 把提示添加到异步列队,同步代码可取消异步
const delayMessage = (msg, { type, langs }) => {
  if (msg) {
    let yes = true
    const asyncMsg = () => yes && Message[type](tplLang(msg, langs))
    const stopMsg = () => (yes = false)
    setTimeout(asyncMsg)
    return stopMsg
  }
  return null
}
http.interceptors.request.use(
  cfg => {
    const { data, method, url } = cfg
    //控制并发
    const source = cancelToken.source()
    //排除不需要并发控制的接口
    // const cancelKey = method + url + (ccc.includes(url) ? hashcode() : '')
    let cancelKey = method + url
    const skey = data.get('selectKey') || ''
    ccc.includes(url) && skey && (cancelKey += '/' + skey)
    cfg.cancelToken = source.token
    cancelTokenMap.has(cancelKey)
      ? source.cancel(cancelMsg(cancelKey))
      : cancelTokenMap.set(cancelKey, source.cancel)

    //登录处理, 删除token
    const isLogin = url === '/doGoSns'
    isLogin && clearToken()

    //数据处理, json或formdata, 添加token
    const token = getToken()
    token && data && data.append('token', token)

    return cfg
  },
  err => {
    console.log('request err:', err)
    const { message } = err
    return Promise.reject({
      err,
      cancelMessage: delayMessage(message, { type: 'error' })
    })
  }
)

http.interceptors.response.use(
  res => {
    console.log('response res:', res.config.url)
    const { data, config } = res
    // 退出并发控制队列
    deleteCancelTokenMap(config)
    if (data) {
      const { responseType } = config

      if (responseType === 'blob') {
        const cd = res.headers['content-disposition'] || ''
        downfile(URL.createObjectURL(data), cd.split('=')[1])
      }

      const { code, content, msg } = data
      if (code === 200)
        return {
          code: 200,
          data: content,
          cancelMessage: delayMessage(msg, { type: 'success' })
        }
      if (code === 201) {
        content && downfile(fileurl(content))
        return { code: 301 }
      }

      if (code === -1) {
        const option = { type: 'error' }
        if (content) {
          const { type, langs, fileCode } = content

          langs && (option.langs = langs)

          if (type === -2 && fileCode) {
            downfile(fileurl(fileCode))
          }
          if (type === 402) {
            //取消所有请求
            clearCancelTokenMap()
            //清空登录信息
            clearLoginInfo()
            // 返回登录
            toRoute(Routes.Login)
          }
        }
        return {
          code: 500,
          cancelMessage: delayMessage(msg, option)
        }
      }
      return {
        code: 400
      }
    }
    return res
  },
  err => {
    const { config, message } = err
    console.log('response err:', message)
    const error = { message }
    if (!Axios.isCancel(err)) {
      deleteCancelTokenMap(config)
      error.cancelMessage = delayMessage(message, { type: 'error' })
    }
    return Promise.reject(error)
  }
)

const toFormData = data => {
  if (data instanceof FormData) return data
  const fd = new FormData()
  data &&
    ObjectKeys(data).forEach(key => {
      const val = data[key]
      switch (typeof val) {
        case 'string':
          val && fd.append(key, val)
          break
        case 'number':
          ;(val || val === 0) && fd.append(key, val)
          break
        case 'boolean':
          fd.append(key, val)
          break
        case 'object':
          if (IsArray(val)) {
            val.length && fd.append(key, val)
          } else if (val instanceof File) {
            fd.append(key, val)
          } else if (val !== null) {
            fd.append(key, JSON.stringify(val))
          }
          break
      }
    })
  return fd
}

export const post = (url, data, config) =>
  http.post(url, toFormData(data), config)

export const get = (url, params, config) => http.get(url, { params, ...config })

export const down = async (...args) => {
  const { data } = await post(...args)
  data && downfile(fileurl(data))
}
